package io.kiki.sba.registry.server.metadata.cleaner;


import io.kiki.sba.registry.cache.ConsecutiveSuccess;
import io.kiki.sba.registry.common.model.console.PersistenceData;
import io.kiki.sba.registry.common.model.console.PersistenceDataBuilder;
import io.kiki.sba.registry.common.model.constants.ValueConstants;
import io.kiki.sba.registry.common.model.store.AppRevision;
import io.kiki.sba.registry.server.metadata.MetadataLeaderService;
import io.kiki.sba.registry.server.metadata.bootstrap.config.ServerConfig;
import io.kiki.sba.registry.server.metadata.provide.data.ProvideDataService;
import io.kiki.sba.registry.store.api.DBResponse;
import io.kiki.sba.registry.store.api.OperationStatus;
import io.kiki.sba.registry.store.api.date.DateNowRepository;
import io.kiki.sba.registry.store.api.repository.AppRevisionRepository;
import io.kiki.sba.registry.store.api.repository.InterfaceAppsRepository;
import io.kiki.sba.registry.store.jdbc.config.MetadataConfig;
import io.kiki.sba.registry.util.ConcurrentUtils;
import io.kiki.sba.registry.util.StringFormatter;
import io.kiki.sba.registry.util.WakeUpLoopExecuteTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class InterfaceAppsIndexCleaner implements MetadataLeaderService.LeaderElectorListener, ApplicationListener<ContextRefreshedEvent> {

    private static final Logger logger = LoggerFactory.getLogger(InterfaceAppsIndexCleaner.class);

    private static final boolean DEFAULT_ENABLED = false;

    final Renewer renewer = new Renewer();

    final Cleaner cleaner = new Cleaner();

    @Autowired
    AppRevisionRepository appRevisionRepository;

    @Autowired
    InterfaceAppsRepository interfaceAppsRepository;

    @Autowired
    MetadataLeaderService metadataLeaderService;

    @Autowired
    MetadataConfig metadataConfig;

    @Autowired
    ProvideDataService provideDataService;

    @Autowired
    DateNowRepository dateNowRepository;

    @Autowired
    ServerConfig serverConfig;

    ConsecutiveSuccess consecutiveSuccess;

    public InterfaceAppsIndexCleaner() {
    }

    public InterfaceAppsIndexCleaner(MetadataLeaderService metadataLeaderService) {
        this.metadataLeaderService = metadataLeaderService;
    }

    @PostConstruct
    public void init() {
        consecutiveSuccess = new ConsecutiveSuccess(3, (long) metadataConfig.getRevisionRenewIntervalMinutes() * 60 * 1000 * 5);
        metadataLeaderService.registerListener(this);
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        start();
    }

    public void start() {
        ConcurrentUtils.createDaemonThread(InterfaceAppsIndexCleaner.class.getSimpleName() + "-renewer", renewer).start();
        ConcurrentUtils.createDaemonThread(InterfaceAppsIndexCleaner.class.getSimpleName() + "-cleaner", cleaner).start();
        logger.info("InterfaceAppsIndexCleaner started");
    }

    protected AppRevision revisionConvert(AppRevision revision) {
        return revision;
    }

    public void renew() {
        if (!metadataLeaderService.amILeader()) {
            return;
        }
        try {
            long start = 0;
            int page = 100;
            Map<String, Set<String>> mappings = new HashMap<>();
            while (true) {
                List<AppRevision> revisions = appRevisionRepository.listFromStorage(start, page);
                for (AppRevision revision : revisions) {
                    start = Math.max(start, revision.getId());
                    if (revision.isDeleted()) {
                        continue;
                    }

                    String appName = revision.getAppName();
                    for (String interfaceName : revision.getInterfaceMap().keySet()) {
                        mappings.computeIfAbsent(appName, k -> new HashSet<>()).add(interfaceName);
                    }
                }
                if (revisions.size() < page) {
                    break;
                }
            }
            for (Map.Entry<String, Set<String>> entry : mappings.entrySet()) {
                String appName = entry.getKey();
                for (String interfaceName : entry.getValue()) {
                    interfaceAppsRepository.renew(interfaceName, appName);
                    ConcurrentUtils.sleepUninterruptibly(10, TimeUnit.MILLISECONDS);
                }
            }
            logger.info("renew interface apps index succeed");
            consecutiveSuccess.success();
        } catch (Throwable e) {
            consecutiveSuccess.fail();
            logger.error("renew interface apps index failed:", e);
        }
    }

    private boolean isEnabled() {
        DBResponse<PersistenceData> ret = provideDataService.queryProvideData(ValueConstants.INTERFACE_APP_CLEANER_ENABLED_DATA_ID);
        if (ret.getOperationStatus() == OperationStatus.success) {
            PersistenceData data = ret.getEntity();
            return Boolean.parseBoolean(data.getData());
        }
        return DEFAULT_ENABLED;
    }

    public void setEnabled(boolean enabled) {
        PersistenceData persistenceData = PersistenceDataBuilder.createPersistenceData(ValueConstants.INTERFACE_APP_CLEANER_ENABLED_DATA_ID, Boolean.toString(enabled));
        try {
            provideDataService.saveProvideData(persistenceData);
        } catch (Exception e) {
            logger.error("set interface app cleaner failed: ", e);
            throw new RuntimeException(StringFormatter.format("set interface app cleaner failed: {}", e.getMessage()));
        }
    }

    Date dateBeforeNow(int minutes) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(dateNowRepository.getNow());
        calendar.add(Calendar.MINUTE, -minutes);
        return calendar.getTime();
    }

    void cleanup() {
        if (!metadataLeaderService.amILeader()) {
            return;
        }
        if (!consecutiveSuccess.check()) {
            return;
        }
        int count = interfaceAppsRepository.cleanDeleted(dateBeforeNow(metadataConfig.getInterfaceAppsIndexRenewIntervalMinutes() * 15), serverConfig.getInterfaceMaxRemove());
        if (count > 0) {
            logger.info("clean up {} interface app", count);
        }
    }

    public void startRenew() {
        renewer.wakeup();
    }

    public void startCleaner() {
        cleaner.wakeup();
    }

    @Override
    public void becomeLeader() {
        consecutiveSuccess.clear();
    }

    @Override
    public void loseLeader() {
        consecutiveSuccess.clear();
    }

    final class Renewer extends WakeUpLoopExecuteTask {
        @Override
        public int getWaitingMillis() {
            return (metadataConfig.getInterfaceAppsIndexRenewIntervalMinutes() * 60 * 1000);
        }

        @Override
        public void _execute_() {
            renew();
        }
    }

    final class Cleaner extends WakeUpLoopExecuteTask {
        @Override
        public int getWaitingMillis() {
            int base = metadataConfig.getInterfaceAppsIndexRenewIntervalMinutes() * 1000 * 60;
            return (int) (base + Math.random() * base);
        }

        @Override
        public void _execute_() {
            if (!isEnabled()) {
                return;
            }
            cleanup();
        }
    }
}
